package org.boulderfoodrescue.webapp.client.pickuptimes.timepicker;

import com.google.gwt.core.client.GWT;
import com.google.gwt.editor.client.IsEditor;
import com.google.gwt.editor.client.LeafValueEditor;
import com.google.gwt.editor.client.adapters.TakesValueEditor;
import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.FocusEvent;
import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.regexp.shared.RegExp;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HasValue;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Widget;

public class TimePicker extends Composite implements HasValue<Time>,
IsEditor<LeafValueEditor<Time>>{

	private static TimePickerUiBinder uiBinder = GWT
			.create(TimePickerUiBinder.class);

	interface TimePickerUiBinder extends UiBinder<Widget, TimePicker> {
	}

	private class DateBoxHandler implements ValueChangeHandler<Time>,
	FocusHandler, BlurHandler, ClickHandler, KeyDownHandler,
	CloseHandler<PopupPanel> {
		public void onClose(CloseEvent<PopupPanel> event) {
			if(allowTPShow)
				updateTimeFromTextBox();
		}

		public void onKeyDown(KeyDownEvent event) {
			int key = event.getNativeKeyCode();
			if(key == KeyCodes.KEY_DOWN){
				picker.up();
			} else if(key == KeyCodes.KEY_UP){
				picker.down();
			} else{
				hideTimePicker();
			}
		}

		public void onClick(ClickEvent event) {
			showTimePicker();
		}

		public void onBlur(BlurEvent event) {
			updateTimeFromTextBox();
		}

		public void onFocus(FocusEvent event) {
			if(allowTPShow)
				showTimePicker();
		}

		public void onValueChange(ValueChangeEvent<Time> event) {
			setValue(parseTime(), event.getValue(), true, true);
			hideTimePicker();
			preventTimePickerPopup();
			box.setFocus(true);
		}
	}
	/**
	 * Default style name added when the date box has a format error.
	 */
	private static final String DATE_BOX_FORMAT_ERROR = "dateBoxFormatError";

	/**
	 * Default style name.
	 */
	public static final String DEFAULT_STYLENAME = "gwt-DateBox";
	private final PopupPanel popup;
	@UiField
	protected TextBox box;
	private final TimeTable picker;
	private LeafValueEditor<Time> editor;
	private boolean allowTPShow = true;

	public TimePicker() {
		this(new Time(12,0));
	}

	public TimePicker(Time default_time){
		this(default_time, new Time(0,0), new Time(24,0));
	}

	public TimePicker(Time default_time, Time range_min, Time range_max){
		this(default_time, range_min, range_max, 30);
	}

	public TimePicker(Time default_time, Time range_min, Time range_max, int interval){
		initWidget(uiBinder.createAndBindUi(this));
		assert range_max.getMinuteCount() >= range_min.getMinuteCount() : "End time must be greater than or equal to start time";
		this.popup = new PopupPanel(true);
		this.picker = new TimeTable(new Time(0,0), new Time(24,0), 30);
		popup.addAutoHidePartner(box.getElement());
		popup.setWidget(picker);

		DateBoxHandler handler = new DateBoxHandler();
		picker.addValueChangeHandler(handler);
		box.addFocusHandler(handler);
		box.addBlurHandler(handler);
		box.addClickHandler(handler);
		box.addKeyDownHandler(handler);
		popup.addCloseHandler(handler);
		setValue(default_time);

		this.setStyleName("gwt-ui-timepicker");
	}

	@Override
	public HandlerRegistration addValueChangeHandler(
			ValueChangeHandler<Time> handler) {
		return addHandler(handler, ValueChangeEvent.getType());
	}

	@Override
	public LeafValueEditor<Time> asEditor() {
		if (editor == null) {
			editor = TakesValueEditor.of(this);
		}
		return editor;
	}

	public TimeTable getTimeTable() {
		return picker;
	}

	public TextBox getTextBox() {
		return box;
	}

	@Override
	public Time getValue() {
		return parseTime();
	}

	public void showTimePicker() {
		Time current = parseTime();
		if (current == null) {
			current = new Time(12,0);
		}
		popup.showRelativeTo(this);
		picker.onShow(current);
	}
	public void hideTimePicker(){
		popup.hide();
	}
	private void updateTimeFromTextBox() {
		Time time = parseTime();
		setValue(picker.getValue(), time, true, false);
	}

	private Time parseTime() {
		String time = box.getText().trim();
		time.replace("\\s+", " ");
		RegExp regExp = RegExp.compile("^((([0]?[1-9]|1[0-2])(:|\\.)[0-5][0-9]" +
				"( )?(AM|am|aM|Am|PM|pm|pM|Pm))|(([0]?[0-9]|1[0-9]|2[0-3])(:|\\.)" +
				"[0-5][0-9]))$");
		boolean matchFound = regExp.test(time);
		if(!matchFound){
			box.addStyleName(DATE_BOX_FORMAT_ERROR);
			return null;
		}
		box.removeStyleName(DATE_BOX_FORMAT_ERROR);
		regExp = RegExp.compile("(AM|am|aM|Am|PM|pm|pM|Pm)");
		Boolean clock_12hr = regExp.test(time);
		Boolean pm = false;
		if(clock_12hr){
			regExp = RegExp.compile("(PM|pm|pM|Pm)");
			if(regExp.test(time))
				pm = true;
		}
		String[] times = time.split("(:|\\.)");
		int hr = Integer.parseInt(times[0]);
		if(clock_12hr && hr == 12)
			hr = 0;
		if(pm)
			hr += 12;
		int min;
		min = Integer.parseInt(times[1].substring(0, 2));
		return new Time(hr, min);
	}

	private void preventTimePickerPopup(){
		allowTPShow = false;
		DeferredCommand.addCommand(new Command(){
			public void execute(){
				allowTPShow = true;
			}
		});
	}

	@Override
	public void setValue(Time time) {
		setValue(time, false);
	}

	@Override
	public void setValue(Time time, boolean fireEvents){
		setValue(picker.getValue(), time, fireEvents, true);
	}

	private void setValue(Time oldTime, Time time, boolean fireEvents, boolean updateText){
		picker.setValue(time, false);
		if(updateText){
			box.setText(time.toStringTimeShort());
		}
	}
}
